﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;

namespace Extensions
{
    public static class ReaderWriterExtensions
    {
        static void Write (this BinaryWriter writer, DateTime dt)
        {
            writer.Write (dt.ToBinary ());
        }

        static DateTime ReadDateTime (this BinaryReader reader)
        {
            return DateTime.FromBinary (reader.ReadInt64 ());
        }
    }
}

namespace TEAA
{
    public class BinaryRecordManager: RecordManager
    {
        private static int SLOTINFO_BYTES = 2; //ushort
        public static string RECORDID_FIELD_NAME = "RecordID";
        private SystemManager systemManager;
        private BufferManager bufferManager;

        private static RecordManager recordManager;

        public static RecordManager GetRecordManager()
        {
            if (recordManager == null)
                recordManager = new BinaryRecordManager();
            return recordManager;
        }

        private BinaryRecordManager()
        {
            systemManager = SystemManager.GetSystemManager();
            bufferManager = BufferManager.GetBufferManager();
        }

        private int recordLength(string table)
        {
            //int result = 0;

            //foreach (string attribute in systemManager.GetAttributes(table))
            //    {
            //        result = result + systemManager.GetAttributeLength(table, attribute);
            //    }
            //return result;
            return systemManager.GetTableRowLength(table);
        }

        public override DataSet getAll(string table)
        {

            DataTable dataTable = new DataTable(table);
            dataTable.Columns.Add(RECORDID_FIELD_NAME, typeof(RecordID));
            foreach (string attribute in systemManager.GetAttributes(table))
            {
                dataTable.Columns.Add(attribute,DataTypeUtils.GetType(systemManager.GetAttributeDataType(table,attribute)));
            }

            uint lastPageNo = getLastPage(table);            
            for (uint pageNo = 1; pageNo <= lastPageNo; pageNo++)
            {
                //byte[] currentPage = bufferManager.getPage(table, pageNo);
                uint pageSize = (uint)SystemProperties.BufferPageSize; // ezt majd  ki lehetne cserélni
                //MemoryStream lastPageMemoryStream = new MemoryStream(currentPage);
                BinaryReader pageInfo = bufferManager.getBinaryReader(table, pageNo);
                pageInfo.BaseStream.Seek(pageSize - SLOTINFO_BYTES, SeekOrigin.Begin);
                uint usedSlots = pageInfo.ReadUInt16();

                for (int slotDir = 1; slotDir <= usedSlots; slotDir++)
                {
                    pageInfo.BaseStream.Seek(pageSize - (SLOTINFO_BYTES * (slotDir+1)), SeekOrigin.Begin); //slotDir+1 - SlotDir+usedSlots
                    ushort slotNo = pageInfo.ReadUInt16();
                    RecordID recordID = new RecordID(pageNo, slotNo);
                    pageInfo.BaseStream.Seek(recordLength(table) * (slotNo-1), SeekOrigin.Begin);
                    
                    //Record currentRecord = getRecord(table, recordID);
                    DataRow dr = dataTable.NewRow();
                    int fieldNo = 0;
                    dr.SetField<RecordID>(fieldNo,recordID);
                    foreach (string attribute in systemManager.GetAttributes(table))
                    //foreach (DataColumn dc in dataTable.Columns)
                    {
                        fieldNo++;
                        //BinaryReader br = new BinaryReader(new MemoryStream(dc.value));
                        switch (systemManager.GetAttributeDataType(table,attribute)) 

                        {
                            case DataType.Boolean:
                                dr.SetField<bool>(fieldNo,pageInfo.ReadBoolean());
                                break;
                            case DataType.Integer:
                                dr.SetField<int>(fieldNo, pageInfo.ReadInt32());
                                break;
                            case DataType.Decimal:
                                dr.SetField<decimal>(fieldNo, pageInfo.ReadDecimal());
                                break;

                            case DataType.Character:
                                dr.SetField<char>(fieldNo, pageInfo.ReadChar());
                                break;
                            case DataType.Text:
                                dr.SetField<string>(fieldNo, pageInfo.ReadString());
                                break;
                            case DataType.DateTime:
                                DateTime result = DateTime.FromBinary(pageInfo.ReadInt64());
                                dr.SetField<DateTime>(fieldNo, result);
                                break;
                        }

             
                    }
                    dataTable.Rows.Add(dr);
                                        
                }

                

            }
            DataSet dataSet = new DataSet();
            dataSet.Tables.Add(dataTable);
            return dataSet;
        }
    /*
        public override Record getRecord(string table, RecordID recordID)
        {
            Record record = new Record(table);
            record.recordID = recordID;
            byte[] pageArray = bufferManager.getPage(table,recordID.page);

            Stream recordStream = new MemoryStream(pageArray);
            recordStream.Seek(recordID.slot * recordLength(table), SeekOrigin.Begin);
            //int readIndex = 0;
                
                foreach (string attribute in systemManager.GetAttributes(table))
                { 
                   //int attributeLength = systemManager.GetAttributeLength(table,attribute);
                   int attributeLength = DataTypeUtils.GetDataTypeLength(systemManager.GetAttributeDataType(record.table, attribute));
                   byte[] value = new byte[attributeLength];
                   recordStream.Read(value,0,attributeLength);
                   record.fields.Add(new Field(attribute,systemManager.GetAttributeDataType(table, attribute), value));
                   //readIndex = readIndex + attributeLength; 
                }
            
            return record;
        }
*/

        public override bool insert(DataSet dataSet)
        {
            foreach (DataTable dataTable in dataSet.Tables)
            {

                String table = dataTable.TableName;
                foreach (DataRow dr in dataTable.Rows)
                {

                    RecordID recordID = findFreePlace(table);
                    
                    BinaryWriter bw = bufferManager.getBinaryWriter(table, recordID.page);
                    bw.BaseStream.Seek((recordID.slot-1) * recordLength(table), SeekOrigin.Begin); // 1- el kezdodik a slot

                    foreach (DataColumn dc in dataTable.Columns)
                    {
                        DataType dataType = DataTypeUtils.GetDataType(dc.DataType);
                        

                        if (dc.ColumnName.Equals(RECORDID_FIELD_NAME))
                        {

                        }

                        else
                        {
                            switch (dataType)
                            {
                                case DataType.Boolean:
                                    bw.Write(dr.Field<bool>(dc));
                                    break;
                                case DataType.Integer:
                                    bw.Write(dr.Field<int>(dc));
                                    break;
                                case DataType.Decimal:
                                    bw.Write(dr.Field<decimal>(dc));
                                    break;
                                case DataType.Character:
                                    bw.Write(dr.Field<char>(dc));
                                    break;
                                case DataType.Text:
                                    String value = dr.Field<string>(dc);
                                    int length = DataTypeUtils.GetDataTypeLength(DataType.Text);
                                    if (value.Length < length)
                                    {
                                        value.PadLeft(length);
                                    }
                                    else if (value.Length > length)
                                    {
                                        value = value.Substring(0, length);
                                    }

                                    bw.Write(value);
                                    break;
                                case DataType.DateTime:
                                    DateTime dateTime = dr.Field<DateTime>(dc);
                                    bw.Write(dateTime.ToBinary());
                                    break;
                            }

                        }
                    }
                    bufferManager.setUpdated(table, recordID.page);
                    bufferManager.flushAll();
                    
                }
            }
            return true;
        }

        public override bool modify(DataSet dataSet)
        {
            foreach (DataTable dataTable in dataSet.Tables)
            {

                String table = dataTable.TableName;
                foreach (DataRow dr in dataTable.Rows)
                {

                    RecordID recordID = dr.Field<RecordID>(RECORDID_FIELD_NAME);

                    BinaryWriter bw = bufferManager.getBinaryWriter(table, recordID.page);
                    bw.BaseStream.Seek((recordID.slot - 1) * recordLength(table), SeekOrigin.Begin);

                    foreach (DataColumn dc in dataTable.Columns)
                    {
                        DataType dataType = DataTypeUtils.GetDataType(dc.DataType);
                       
                        if (dc.ColumnName.Equals(RECORDID_FIELD_NAME))
                        {

                        }

                        else
                        {
                            switch (dataType)
                            {
                                case DataType.Boolean:
                                    bw.Write(dr.Field<bool>(dc));
                                    break;
                                case DataType.Integer:
                                    bw.Write(dr.Field<int>(dc));
                                    break;
                                case DataType.Decimal:
                                    bw.Write(dr.Field<decimal>(dc));
                                    break;
                                case DataType.Character:
                                    bw.Write(dr.Field<char>(dc));
                                    break;
                                case DataType.Text:
                                    String value = dr.Field<string>(dc);
                                    int length = DataTypeUtils.GetDataTypeLength(DataType.Text);
                                    if (value.Length < length)
                                    {
                                        value.PadLeft(length);
                                    }
                                    else if (value.Length > length)
                                    {
                                        value = value.Substring(0, length);
                                    }

                                    bw.Write(value);
                                    break;
                                case DataType.DateTime:
                                    DateTime dateTime = dr.Field<DateTime>(dc);
                                    bw.Write(dateTime.ToBinary());
                                    break;
                            }

                        }
                    }
                    bufferManager.setUpdated(table, recordID.page);
                    bufferManager.flushAll();

                }
            }
            return true;
        }


        public override bool delete(DataSet dataSet)
        {
            foreach (DataTable dt in dataSet.Tables)
            {
                string table = dt.TableName;
                foreach (DataRow dr in dt.Rows)
                {
                    RecordID recordID = dr.Field<RecordID>(RECORDID_FIELD_NAME);
                    delete(table, recordID);
                }
            }
            return true;
        }



        public override bool delete(string table, RecordID recordID)
        {

            uint pageSize = (uint)SystemProperties.BufferPageSize;
            BinaryReader br = bufferManager.getBinaryReader(table, recordID.page);
            BinaryWriter bw = bufferManager.getBinaryWriter(table, recordID.page);
            br.BaseStream.Seek(pageSize - SLOTINFO_BYTES, SeekOrigin.Begin);
            ushort usedSlots = br.ReadUInt16();
            ushort currentSlot=0;
            bool foundSlot = false;
            for (ushort slotNo = 1; slotNo <= usedSlots; slotNo++)
            {
                
                br.BaseStream.Seek(pageSize - SLOTINFO_BYTES*(slotNo+1), SeekOrigin.Begin);
                currentSlot = br.ReadUInt16();

                if (foundSlot)
                {
                    bw.BaseStream.Seek(pageSize - SLOTINFO_BYTES * (slotNo), SeekOrigin.Begin);
                    bw.Write(currentSlot);
                }
                
                if (currentSlot == recordID.slot)
                {
                    foundSlot=true;
                }

               

            }
            if (foundSlot)
            {
                bw.BaseStream.Seek(pageSize - SLOTINFO_BYTES * (usedSlots + 1), SeekOrigin.Begin);
                currentSlot = 0;
                bw.Write(currentSlot);
                usedSlots--;
                bw.BaseStream.Seek(pageSize - SLOTINFO_BYTES, SeekOrigin.Begin);
                bw.Write(usedSlots);
                bufferManager.setUpdated(table, recordID.page);
                return true;
            }
            else return false;
            
        }

        
        RecordID findFreePlace(string table)
        {
            
            uint lastPageNo = getLastPage(table);
            uint pageSize = (uint)SystemProperties.BufferPageSize;

            BinaryReader pageInfo = bufferManager.getBinaryReader(table, lastPageNo);
            pageInfo.BaseStream.Seek(pageSize-SLOTINFO_BYTES, SeekOrigin.Begin);
            ushort usedSlots = pageInfo.ReadUInt16();
            pageInfo.BaseStream.Seek(pageSize-(SLOTINFO_BYTES*(usedSlots+1)), SeekOrigin.Begin); 
            ushort lastSlot = pageInfo.ReadUInt16();
            if ((lastPageNo == 0)||(((usedSlots + 1) * (recordLength(table) + SLOTINFO_BYTES+1) + SLOTINFO_BYTES ) > pageSize))//ha nincs elég hely az oldal végén
            {
                lastPageNo = getLastPage(table,true);
                usedSlots = 0;
                lastSlot = 0;
            }
            usedSlots ++;
            lastSlot ++;

            BinaryWriter pageInfoWriter = bufferManager.getBinaryWriter(table, lastPageNo);
            pageInfoWriter.BaseStream.Seek(pageSize - (SLOTINFO_BYTES * (usedSlots + 1)), SeekOrigin.Begin);
            pageInfoWriter.Write(lastSlot);
            pageInfoWriter.BaseStream.Seek(pageSize - SLOTINFO_BYTES, SeekOrigin.Begin);
            pageInfoWriter.Write(usedSlots);
            return new RecordID(lastPageNo,lastSlot);
            
        }

        public uint getLastPage(string table)
        {
            return getLastPage(table, false);
        }

        public uint getLastPage(string table, bool newPage)
        {

            ushort nextPage;
            ushort lastPage;
            uint lastDirectoryPage = 0;
            BinaryReader directoryStream = bufferManager.getBinaryReader(table,0);
            nextPage = directoryStream.ReadUInt16();
            lastPage = nextPage;

            ushort currentDirectoryEntry = 0;
            while (nextPage != 0)
            {

                if (currentDirectoryEntry == (int)SystemProperties.BufferPageSize / BufferManager.DIRECTORY_PAGEINFO_BYTES)
                {
                    
                    directoryStream = bufferManager.getBinaryReader(table,nextPage);
                    lastDirectoryPage = nextPage;
                    currentDirectoryEntry = 0;
                }
                lastPage = nextPage;
                nextPage = directoryStream.ReadUInt16();                
                currentDirectoryEntry++;
            }
            if (newPage) 
            {
                if (currentDirectoryEntry == (int)SystemProperties.BufferPageSize / BufferManager.DIRECTORY_PAGEINFO_BYTES)
                {
                    lastPage++;
                    BinaryWriter writeStream = bufferManager.getBinaryWriter(table, lastDirectoryPage);
                    if (bufferManager.appendNewPage(table, lastPage))
                    {
                        writeStream.BaseStream.Seek(-1 * BufferManager.DIRECTORY_PAGEINFO_BYTES, SeekOrigin.End);
                        writeStream.Write(lastPage);    //hozzáadja a következő könyvtároldalt a legutóbbi könyvtároldalhoz
                        
                        bufferManager.setUpdated(table, lastDirectoryPage);
                        lastDirectoryPage = lastPage;
                        bufferManager.setUpdated(table, lastDirectoryPage);
                        currentDirectoryEntry = 0;
                    }
                    
                }
                lastPage++;
                
                if (bufferManager.appendNewPage(table, lastPage))
                {
                    BinaryWriter bw = bufferManager.getBinaryWriter(table, lastDirectoryPage);
                    bw.BaseStream.Seek(BufferManager.DIRECTORY_PAGEINFO_BYTES * currentDirectoryEntry, SeekOrigin.Begin);
                    bw.Write(lastPage);
                    bufferManager.setUpdated(table, lastDirectoryPage);
                    bufferManager.setUpdated(table, lastPage);

                   
                }

            }


            return lastPage;

        }


        
    }

}


/*

 * Szemét
 * 
 * 
 *  using (BinaryReader binaryReader = new BinaryReader(new MemoryStream(pageArray)))
            {
                binaryReader.BaseStream.Seek(recordID.slot * recordLength(table),SeekOrigin.Begin);
 * 
 * 
 switch (systemManager.GetAttributeDataType(table, attribute))
                    {
                        case DataType.Boolean:
                            Field field = new Field();
                            field.dataType = DataType.Boolean;
                            field.value = binaryReader.
                            record.fields.Add(new Field
                      

                    }


private Boolean insertRecord(string table, Record record)
        {
            
            RecordID recordID = findFreePlace(table);

            byte[] pageArray = bufferManager.getPage(record.table, recordID.page);

            Stream recordStream = new MemoryStream(pageArray);
            recordStream.Seek(record.recordID.slot * recordLength(record.table), SeekOrigin.Begin);
           // int writeIndex = 0;

            foreach (string attribute in systemManager.GetAttributes(record.table))
            {
                //int attributeLength = systemManager.GetAttributeLength(record.table, attribute);
                int attributeLength = DataTypeUtils.GetDataTypeLength(systemManager.GetAttributeDataType(record.table, attribute));
                byte[] value = new byte[attributeLength];
                Field field = record.fields.Find(
                delegate(Field f)
                {
   
                 return f.name == attribute;
                }
                );
                field.value.CopyTo(value, 0);
                recordStream.Write(value, 0, attributeLength);
                record.fields.Add(new Field(attribute, systemManager.GetAttributeDataType(record.table, attribute), value));
               // writeIndex = writeIndex + attributeLength;
               // recordStream.Seek(attributeLength, SeekOrigin.Begin);
            }
            bufferManager.setUpdated(record.table, recordID.page);
            bufferManager.flushAll();
            return true;
         }

 * 
 * 
         private Boolean modifyRecord(Record record)
        {

        
            byte[] pageArray = bufferManager.getPage(record.table, record.recordID.page);
            

            Stream recordStream = new MemoryStream(pageArray);
            recordStream.Seek(record.recordID.slot * recordLength(record.table), SeekOrigin.Begin);
            int writeIndex = 0;

            foreach (string attribute in systemManager.GetAttributes(record.table))
            {
                //int attributeLength = systemManager.GetAttributeLength(record.table, attribute); 
                int attributeLength = DataTypeUtils.GetDataTypeLength(systemManager.GetAttributeDataType(record.table,attribute));
                byte[] value = new byte[attributeLength];
                Field field = record.fields.Find(
                delegate(Field f)
                {
                    return f.name == attribute;
                }
                );
                field.value.CopyTo(value, 0);
                recordStream.Write(value, writeIndex, attributeLength);
                record.fields.Add(new Field(attribute, systemManager.GetAttributeDataType(record.table, attribute), value));
                writeIndex = writeIndex + attributeLength;
            }
            bufferManager.setUpdated(record.table, record.recordID.page);
            return true;
        }


        public override bool modify(DataTable dataTable)
        {
            String tableName = dataTable.TableName;
            foreach (DataRow dr in dataTable.Rows)
            {
                Record record = new Record(tableName);

                foreach (DataColumn dc in dataTable.Columns)
                {
                    DataType dataType = DataTypeUtils.GetDataType(dc.DataType);
                    byte[] fieldValue = new byte[DataTypeUtils.GetDataTypeLength(dataType)];
                    BinaryWriter bw = new BinaryWriter(new MemoryStream(fieldValue));

                    if (dc.ColumnName.Equals(RECORDID_FIELD_NAME)) 
                    {
                        record.recordID = dr.Field<RecordID>(dc);
                    }

                    else switch (dataType)
                    {
                        case DataType.Boolean:
                            bw.Write(dr.Field<bool>(dc));
                            break;
                        case DataType.Integer:
                            bw.Write(dr.Field<int>(dc));
                            break;
                        case DataType.Decimal:
                            bw.Write(dr.Field<decimal>(dc));
                            break;
                        case DataType.Character:
                            bw.Write(dr.Field<char>(dc));
                            break;
                        case DataType.Text:
                            bw.Write(dr.Field<string>(dc));
                            break;
                        case DataType.DateTime:
                            bw.Write(dr.Field<string>(dc));
                            break;
                    }

                    bw.Write(dr.Field<string>(dc));

                    Field field = new Field(dc.ColumnName, dataType, fieldValue);
                    record.fields.Add(field);
                }

                modifyRecord(record);
            }
            return true;
        }   
 * 
 * 
 * 
*/